# -*- coding: utf-8 -*-
""" Compomer Factory class
"""

__author__ = """Alexander Artyomenko (artyomenkoav@gmail.com)"""
#   Copyright (C) 2011-2012 by
#   Alex Artyomenko <artyomenkoav@gmail.com>
#   All rights reserved.
#   BSD license.
from cvariant import CompomerVariant
from compomer import Compomer

class CompomerFactory(dict):
    """ Class for building masses table of all possible compomers 
        with  limited size ($tsize)
    """
    delta = 0.99
    masses = {"A":329.21, "C":289.18, "G":345.21, "T":304.20}
    nucl_map = {0:"A", 1:"C", 2:"G", 3:"T"}
    cut_site_masses = {"A":0, "C":324.21, "G":0, "T":325.20}
    compomers = {"A":{}, "C":{}, "G":{}, "T":{}}
    

    def __init__(self, tsize):
        """ Initialize new factory. Build table of compomers
            masses order masses by ascending store values in
            self object
        """
        ttable = self.populate_compomers_table(tsize)
        self.save_ordered_map(ttable)
        map(lambda x : self.save_compomer_map(ttable, x), self.compomers.keys())
        print "Compomer Factory is ready to use."
    
    def get_mass(self, i, j, k, l):
        return self.masses["A"] * i + self.masses["C"] * j + self.masses["G"] * k + self.masses["T"] * l
    def get_mass_c(self, compomer):
        return sum(map(lambda x: self.masses[x[0]] * x[1], compomer.items()))

    def find_closeset_compomers(self, cleave_nucl, mass):
        tmp = []
        pr_m = mass - self.delta
        next_m = mass + self.delta
        filtered = filter(lambda x : (x >= pr_m) & (x <= next_m), self.keys())
        for mas in filtered:
            tmp.extend(self[mas])
        return tmp

    def get_compomer_variants(self, cleave_nucl, mass):
        """ Choose compomers from table according to mass and cleave
            nucleotide
        """
        cut_mass = mass - self.cut_site_masses[cleave_nucl]
        tmp = self.find_closeset_compomers(cleave_nucl, cut_mass) 
        filtered = filter(lambda x : x[cleave_nucl] == 0, tmp)   
        result = []
        for nucl in filtered:
            tm = self.get_mass_c(nucl) + self.cut_site_masses[cleave_nucl]
            nucl[cleave_nucl] = 1;
            result.append(CompomerVariant(nucl, tm))
        if len(result) == 0:
            return map(lambda x : CompomerVariant(x, self.get_mass_c(x)), \
                       self.find_closeset_compomers(cleave_nucl, mass)) 
        return result

    def populate_compomers_table(self, tsize):
        ttable = {}
        for i in range(0, tsize):
            for j in range(0, tsize - i):
                for k in range(0, tsize - i - j):
                    for l in range(0, tsize - i - j - k):
                        ttable[i, j, k, l] = self.get_mass(i, j, k, l)
        
        return ttable


    def save_ordered_map(self, ttable):
        filtered = filter((lambda k:reduce(lambda z, y:z | y, map(lambda x:x == 0, k))), ttable.keys())
        # self.sorted_masses = sorted(map(lambda x:ttable[x], filtered))
        for it in filtered:
            val = ttable[it]
            nk = {"A":it[0], "C":it[1], "G":it[2], "T":it[3]}
            if self.has_key(val):
                self[val].append(nk)
            else:
                self[val] = [nk]
    def save_compomer_map(self, ttable, cleave_nucl):
        index = 0;
        while self.nucl_map[index] != cleave_nucl:
            index = index + 1
        filtered = filter(lambda x : x[index] == 0, ttable.keys())
        for it in filtered:
            val = ttable[it]
            nk = {"A":it[0], "C":it[1], "G":it[2], "T":it[3]}
            if self.compomers[cleave_nucl].has_key(val):
                self.compomers[cleave_nucl][val].compomers.append(CompomerVariant(nk, val))
            else:
                self.compomers[cleave_nucl][val] = Compomer(val, [CompomerVariant(nk, val)])
        
